﻿//TraderHelper.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo
{
	using System;
	using System.Collections.Generic;
	using System.Data;
	using System.Linq;
	using System.ServiceModel;
	using System.Threading;

	using Ecng.Net;
	using Ecng.Common;
	using Ecng.ComponentModel;
	using Ecng.Collections;

	using StockSharp.Algo.PnL;
	using StockSharp.Algo.Positions;
	using StockSharp.Algo.Storages;
	using StockSharp.Algo.Strategies;
	using StockSharp.Algo.Testing;
	using StockSharp.BusinessEntities;
	using StockSharp.Logging;

	using Wintellect.PowerCollections;

	/// <summary>
	/// Правила округления цены.
	/// </summary>
	public enum ShrinkRules
	{
		/// <summary>
		/// Автоматически определять, к меньшему или большему значению округлять.
		/// </summary>
		Auto,

		/// <summary>
		/// Округлять к меньшему значению.
		/// </summary>
		Less,

		/// <summary>
		/// Округлять к большему значению.
		/// </summary>
		More,
	}

	/// <summary>
	/// Вспомогательный класс для предоставления различной алгоритмической функциональности.
	/// </summary>
	public static class TraderHelper
	{
		/// <summary>
		/// Получить стакан котировок для заданного инструмента.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо получить стакан.</param>
		/// <returns>Cтакан.</returns>
		public static MarketDepth GetMarketDepth(this Security security)
		{
			return security.CheckTrader().GetMarketDepth(security);
		}

		/// <summary>
		/// Получить отфильтрованный стакан инструмента от всех активных заявок.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо получить стакан.</param>
		/// <param name="orders">Заявки, которые необходимо игнорировать.</param>
		/// <returns>Отфильтрованный стакан.</returns>
		public static IEnumerable<Quote> GetFilteredQuotes(this Security security, IEnumerable<Order> orders)
		{
			return security.GetMarketDepth().SyncGet(c => c.GetFilteredQuotes(orders));
		}

		/// <summary>
		/// Получить отфильтрованный стакан от всех активных заявок.
		/// </summary>
		/// <param name="quotes">Исходный стакан, который необходимо отфильтровать.</param>
		/// <param name="orders">Заявки, которые необходимо игнорировать.</param>
		/// <returns>Отфильтрованный стакан.</returns>
		public static IEnumerable<Quote> GetFilteredQuotes(this IEnumerable<Quote> quotes, IEnumerable<Order> orders)
		{
			if (quotes == null)
				throw new ArgumentNullException("quotes");

			if (orders == null)
				throw new ArgumentNullException("orders");

			var firstQuote = quotes.FirstOrDefault();

			if (firstQuote == null)
				return new Quote[0];

			var trader = firstQuote.Security.Trader;

			if (trader == null)
				throw new ArgumentException("Котировка не имеет информацию о шлюзе.");

			// pyh GetFilteredQuotes для EmulationTrader пропускает процедуру фильтрации т.к. наш симуляционный ордер в стакан не попадает 
			if (trader is IEmulationTrader)
				return quotes;

			return quotes.GetFilteredQuotes(trader.Orders.Where(o => o.State == OrderStates.Active || o.State == OrderStates.None), orders);
		}

		/// <summary>
		/// Отфильтровать стакан от собственных заявок.
		/// </summary>
		/// <param name="quotes">Исходный стакан, который необходимо отфильтровать.</param>
		/// <param name="ownOrders">Активные заявки по данному инструменту.</param>
		/// <param name="orders">Заявки, которые необходимо игнорировать.</param>
		/// <returns>Отфильтрованный стакан.</returns>
		public static IEnumerable<Quote> GetFilteredQuotes(this IEnumerable<Quote> quotes, IEnumerable<Order> ownOrders, IEnumerable<Order> orders)
		{
			if (quotes == null)
				throw new ArgumentNullException("quotes");

			if (ownOrders == null)
				throw new ArgumentNullException("ownOrders");

			if (orders == null)
				throw new ArgumentNullException("orders");

			var dict = new MultiDictionary<Tuple<OrderDirections, decimal>, Order>(false);

			foreach (var order in ownOrders)
			{
				dict.Add(new Tuple<OrderDirections, decimal>(order.Direction, order.Price), order);
			}

			var retVal = new List<Quote>(quotes.Select(q => q.Clone()));

			foreach (var quote in retVal.ToArray())
			{
				var o = dict.TryGetValue(new Tuple<OrderDirections, decimal>(quote.OrderDirection, quote.Price));

				if (o != null)
				{
					foreach (var order in o)
					{
						if (!orders.Contains(order))
							quote.Volume -= order.Balance;
					}

					if (quote.Volume <= 0)
						retVal.Remove(quote);
				}
			}

			return retVal;
		}

		/// <summary>
		/// Получить рыночную цену для инструмента.
		/// </summary>
		/// <param name="security">Инструмент, по которому вычисляется рыночная цена.</param>
		/// <param name="direction">Направление заявки.</param>
		/// <returns>Рыночная цена. Если по инструменту возможна регистрация заявок типа <see cref="OrderTypes.Market"/>, будет возвращено 0.</returns>
		public static decimal GetMarketPrice(this Security security, OrderDirections direction)
		{
			var board = security.CheckExchangeBoard();

			if (board.IsSupportMarketOrders)
				return 0;

			if (direction == OrderDirections.Buy && security.MaxPrice != 0)
				return security.MaxPrice;
			else if (direction == OrderDirections.Sell && security.MinPrice != 0)
				return security.MinPrice;
			else
				throw new ArgumentException("У инструмента {0} отсутствует информация о планках.".Put(security), "security");
		}

		/// <summary>
		/// Высчитать текущую цену по инструменту в зависимости от направления заявки.
		/// </summary>
		/// <param name="security">Инструмент, по которому вычисляется текущая цена.</param>
		/// <param name="direction">Направление заявки.</param>
		/// <param name="priceType">Тип рыночной цены.</param>
		/// <param name="orders">Заявки, которые необходимо игнорировать.</param>
		/// <returns>Текущая цена. Если информации в стакане недостаточно, будет возвращено 0.</returns>
		public static Unit GetCurrentPrice(this Security security, OrderDirections? direction = null, MarketPriceTypes priceType = MarketPriceTypes.Following, IEnumerable<Order> orders = null)
		{
			var depth = security.GetMarketDepth();

			decimal? currentPrice = null;

			if (direction != null)
			{
				var result = depth.GetCurrentPrice((OrderDirections)direction, priceType, orders);

				if (result != 0)
					return result;

				if (direction == OrderDirections.Buy && security.BestAsk != null)
					currentPrice = security.BestAsk.Price;
				else if (direction == OrderDirections.Sell && security.BestBid != null)
					currentPrice = security.BestBid.Price;
			}

			if (currentPrice == null && security.LastTrade != null)
				currentPrice = security.LastTrade.Price;

			if (currentPrice == null)
				currentPrice = 0;

			return new Unit((decimal)currentPrice) { Security = security };
		}

		/// <summary>
		/// Высчитать текущую цену по стакану в зависимости от направления заявки.
		/// </summary>
		/// <remarks>Для корректной работы метода необходимо запустить экспорт стакана.</remarks>
		/// <param name="depth">Стакан, по которому нужно высчитать текущую цену.</param>
		/// <param name="direction">Направление заявки. Если это покупка, то будет использоваться
		/// значение <see cref="Security.BestAsk"/>, иначе <see cref="Security.BestBid"/>.</param>
		/// <param name="priceType">Тип текущей цены.</param>
		/// <param name="orders">Заявки, которые необходимо игнорировать.</param>
		/// <returns>Текущая цена. Если информации в стакане недостаточно, будет возвращено 0.</returns>
		public static Unit GetCurrentPrice(this MarketDepth depth, OrderDirections direction, MarketPriceTypes priceType = MarketPriceTypes.Following, IEnumerable<Order> orders = null)
		{
			if (depth == null)
				throw new ArgumentNullException("depth");

			if (orders != null)
			{
				var quotes = depth.GetFilteredQuotes(orders);
				depth = new MarketDepth(depth.Security).Update(quotes, depth.LastChangeTime);
			}

			return depth.BestPair.GetCurrentPrice(direction, priceType);
		}

		/// <summary>
		/// Высчитать текущую цену по лучшей паре котировок в зависимости от направления заявки.
		/// </summary>
		/// <remarks>Для корректной работы метода необходимо запустить экспорт стакана.</remarks>
		/// <param name="bestPair">Лучшая пара котировок, по которой вычисляется текущая цена.</param>
		/// <param name="direction">Направление заявки. Если это покупка, то будет использоваться
		/// значение <see cref="Security.BestAsk"/>, иначе <see cref="Security.BestBid"/>.</param>
		/// <param name="priceType">Тип текущей цены.</param>
		/// <returns>Текущая цена. Если информации в стакане недостаточно, будет возвращено 0.</returns>
		public static Unit GetCurrentPrice(this MarketDepthPair bestPair, OrderDirections direction, MarketPriceTypes priceType = MarketPriceTypes.Following)
		{
			if (bestPair == null)
				throw new ArgumentNullException("bestPair");

			Quote quote;

			decimal currentPrice;

			switch (priceType)
			{
				case MarketPriceTypes.Opposite:
					quote = (direction == OrderDirections.Buy ? bestPair.Ask : bestPair.Bid);
					currentPrice = quote == null ? 0 : quote.Price;
					break;
				case MarketPriceTypes.Following:
					quote = (direction == OrderDirections.Buy ? bestPair.Bid : bestPair.Ask);
					currentPrice = quote == null ? 0 : quote.Price;
					break;
				case MarketPriceTypes.Middle:
					if (bestPair.Bid == null || bestPair.Ask == null)
						currentPrice = 0;
					else
						currentPrice = bestPair.Bid.Price + bestPair.SpreadPrice / 2;
					break;
				default:
					throw new ArgumentOutOfRangeException("priceType");
			}

			return new Unit(currentPrice) { Security = bestPair.Security };
		}

		/// <summary>
		/// Применить для цены сдвиг в зависимости от направления <paramref name="direction"/>.
		/// </summary>
		/// <param name="price">Цена.</param>
		/// <param name="direction">Направление заявки, котороые использует в качестве направления для сдвига (для покупки сдвиг прибавляется, для продажи - вычитается).</param>
		/// <param name="offset">Сдвиг цены.</param>
		/// <returns>Новая цена.</returns>
		public static decimal ApplyOffset(this Unit price, OrderDirections direction, Unit offset)
		{
			if (price == null)
				throw new ArgumentNullException("price");

			return price.Security.ShrinkPrice((decimal)(direction == OrderDirections.Buy ? price + offset : price - offset));
		}

		/// <summary>
		/// Обрезать цену для заявки, чтобы она стала кратной минимальному шагу, а так же ограничить количество знаков после запятой.
		/// </summary>
		/// <param name="order">Заявка, для которой будет обрезана цена <see cref="Order.Price"/>.</param>
		/// <param name="rule">Правило округления цены.</param>
		public static void ShrinkPrice(this Order order, ShrinkRules rule = ShrinkRules.Auto)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			order.Price = order.Security.ShrinkPrice(order.Price, rule);
		}

		/// <summary>
		/// Обрезать цену, чтобы она стала кратной минимальному шагу, а так же ограничить количество знаков после запятой.
		/// </summary>
		/// <param name="security">Инструмент, из которого берется значения <see cref="Security.MinStepSize"/> и <see cref="Security.Decimals"/>.</param>
		/// <param name="price">Цена, которую нужно сделать кратной.</param>
		/// <param name="rule">Правило округления цены.</param>
		/// <returns>Кратная цена.</returns>
		public static decimal ShrinkPrice(this Security security, decimal price, ShrinkRules rule = ShrinkRules.Auto)
		{
			security.CheckMinStepSize();

			return price.Round(security.MinStepSize, security.Decimals,
				rule == ShrinkRules.Auto
					? (MidpointRounding?)null
					: (rule == ShrinkRules.Less ? MidpointRounding.AwayFromZero : MidpointRounding.ToEven)).RemoveTrailingZeros();
		}

		/// <summary>
		/// Получить позицию по Моей сделке.
		/// </summary>
		/// <param name="trade">Моя сделка, по которой рассчитывается позиция. При покупке объем сделки <see cref="Trade.Volume"/>
		/// берется с положительным знаком, при продаже - с отрицательным.</param>
		/// <returns>Позиция.</returns>
		public static decimal GetPosition(this MyTrade trade)
		{
			if (trade == null)
				throw new ArgumentNullException("trade");

			return trade.Order.Direction == OrderDirections.Buy ? trade.Trade.Volume : -trade.Trade.Volume;
		}

		/// <summary>
		/// Получить позицию по заявке.
		/// </summary>
		/// <param name="order">Заявка, по которой рассчитывается позиция. При покупке позиция берется с положительным знаком, при продаже - с отрицательным.</param>
		/// <returns>Позиция.</returns>
		public static decimal GetPosition(this Order order)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			var volume = order.GetMatchedVolume();

			return order.Direction == OrderDirections.Buy ? volume : -volume;
		}

		/// <summary>
		/// Получить позицию по портфелю.
		/// </summary>
		/// <param name="portfolio">Портфель, для которого необходимо получить позицию.</param>
		/// <returns>Позиция по портфелю.</returns>
		public static decimal GetPosition(this Portfolio portfolio)
		{
			if (portfolio == null)
				throw new ArgumentNullException("portfolio");

			if (portfolio.Trader == null)
				throw new ArgumentException("Портфель не имеет информацию о шлюзе.");

			return portfolio.Trader.Positions.Filter(portfolio).Sum(p => p.CurrentValue);
		}

		/// <summary>
		/// Получить позицию по Моим сделкам.
		/// </summary>
		/// <param name="trades">Мои сделки, по которым рассчитывается позиция через метод <see cref="GetPosition(StockSharp.BusinessEntities.MyTrade)"/>.</param>
		/// <returns>Позиция.</returns>
		public static decimal GetPosition(this IEnumerable<MyTrade> trades)
		{
			return trades.Sum(t => t.GetPosition());
		}

		/// <summary>
		/// Получить объем заявки, сопоставимый с размером позиции.
		/// </summary>
		/// <param name="position">Позиция по инструменту.</param>
		/// <returns>Объем заявки.</returns>
		public static decimal GetOrderVolume(this Position position)
		{
			if (position == null)
				throw new ArgumentNullException("position");

			return (position.CurrentValue / position.Security.MinLotSize).Abs();
		}

		/// <summary>
		/// Сгруппировать заявки по инструменту и портфелю.
		/// </summary>
		/// <remarks>Рекомендуется использовать для уменьшения транзакционных издержек.</remarks>
		/// <param name="orders">Исходные заявки.</param>
		/// <returns>Сгруппированные заявки.</returns>
		public static IEnumerable<Order> Join(this IEnumerable<Order> orders)
		{
			if (orders == null)
				throw new ArgumentNullException("orders");

			return orders.GroupBy(o => new Tuple<Security, Portfolio>(o.Security, o.Portfolio)).Select(g =>
			{
				Order firstOrder = null;

				foreach (var order in g)
				{
					if (firstOrder == null)
					{
						firstOrder = order;
					}
					else
					{
						var sameDir = firstOrder.Direction == order.Direction;

						firstOrder.Volume += (sameDir ? 1 : -1) * order.Volume;

						if (firstOrder.Volume < 0)
						{
							firstOrder.Direction = firstOrder.Direction.Invert();
							firstOrder.Volume = firstOrder.Volume.Abs();
						}

						firstOrder.Price = sameDir ? firstOrder.Price.GetMiddle(order.Price) : order.Price;
					}
				}

				if (firstOrder == null)
					throw new InvalidOperationException("Заявки отсутствуют.");

				if (firstOrder.Volume == 0)
					return null;

				firstOrder.ShrinkPrice();
				return firstOrder;
			})
			.Where(o => o != null);
		}

		/// <summary>
		/// Рассчитать прибыль-убыток на основе сделок.
		/// </summary>
		/// <param name="trades">Сделки, по которым необходимо рассчитывать прибыль-убыток.</param>
		/// <returns>Прибыль-убыток.</returns>
		public static decimal GetPnL(this IEnumerable<MyTrade> trades)
		{
			return trades.GroupBy(t => t.Trade.Security).Sum(g =>
			{
				var queue = new PnLQueue(g.Key);

				g.OrderBy(t => t.Trade.Time).ForEach(t => queue.Process(t, false));

				return queue.RealizedPnL + queue.UnrealizedPnL;
			});
		}

		/// <summary>
		/// Рассчитать прибыль-убыток для сделки.
		/// </summary>
		/// <param name="trade">Сделка, для которой необходимо рассчитывать прибыль-убыток.</param>
		/// <returns>Прибыль-убыток.</returns>
		public static decimal GetPnL(this MyTrade trade)
		{
			if (trade == null)
				throw new ArgumentNullException("trade");

			return GetPnL(trade.Trade.Price, trade.Trade.Volume, trade.Order.Direction, (decimal)trade.Order.Security.GetCurrentPrice(trade.Order.Direction.Invert()));
		}

		internal static decimal GetPnL(decimal price, decimal volume, OrderDirections direction, decimal marketPrice)
		{
			return (price - marketPrice) * volume * (direction == OrderDirections.Sell ? 1 : -1);
		}

		/// <summary>
		/// Рассчитать прибыль-убыток на основе портфеля.
		/// </summary>
		/// <param name="portfolio">Портфель, для которого необходимо расcчитать прибыль-убыток.</param>
		/// <returns>Прибыль-убыток.</returns>
		public static decimal GetPnL(this Portfolio portfolio)
		{
			if (portfolio == null)
				throw new ArgumentNullException("portfolio");

			return portfolio.CurrentValue - portfolio.BeginValue;
		}

		/// <summary>
		/// Рассчитать стоимость позиции.
		/// </summary>
		/// <param name="position">Позиция.</param>
		/// <returns>Стоимость позиции.</returns>
		public static decimal GetPrice(this Position position)
		{
			if (position == null)
				throw new ArgumentNullException("position");

			var security = position.Security;

			return (decimal)security.GetCurrentPrice(position.GetDirection()) * position.CurrentValue * security.MinStepPrice / security.MinStepSize;
		}

		private static decimal GetPrice(decimal money, CurrencyTypes currency, IEnumerable<Position> positions)
		{
			foreach (var position in positions)
			{
				var price = position.GetPrice();

				if (currency != position.Portfolio.Currency)
					price *= Convert(currency, position.Portfolio.Currency);

				money += price;
			}

			return money;
		}

		/// <summary>
		/// Рассчитать стоимость портфеля.
		/// </summary>
		/// <param name="portfolio">Портфель.</param>
		/// <returns>Стоимость портфеля.</returns>
		public static decimal GetPrice(this Portfolio portfolio)
		{
			if (portfolio == null)
				throw new ArgumentNullException("portfolio");

			var basket = portfolio as BasketPortfolio;

			return basket == null
				? GetPrice(portfolio.GetFreeMoney(), portfolio.Currency, portfolio.Trader.Positions.Filter(portfolio))
				: GetPrice(basket.CurrentValue, basket.Currency, basket.InnerPositions);
		}

		/// <summary>
		/// Рассчитать проскальзывание по сделке. В качестве ожидаемой цены берется <see cref="Order.Price"/>.
		/// </summary>
		/// <param name="trade">Моя сделка, для которой необходимо рассчитать проскальзывание.</param>
		/// <param name="calcNegative">Считать ли отрицательное проскальзывание. Если false, то отрицательное значение проскальзывания заменяется нулем.</param>
		/// <returns>Значение проскальзывания.</returns>
		public static decimal GetSlippage(this MyTrade trade, bool calcNegative = false)
		{
			if (trade == null)
				throw new ArgumentNullException("trade");

			return trade.GetSlippage(trade.Order.Price, calcNegative);
		}

		/// <summary>
		/// Рассчитать проскальзывание по сделке.
		/// </summary>
		/// <param name="trade">Моя сделка, для которой необходимо рассчитать проскальзывание.</param>
		/// <param name="estimatedPrice">Ожидаемая цена заявки.</param>
		/// <param name="calcNegative">Считать ли отрицательное проскальзывание. Если false, то отрицательное значение проскальзывания заменяется нулем.</param>
		/// <returns>Значение проскальзывания.</returns>
		public static decimal GetSlippage(this MyTrade trade, decimal estimatedPrice, bool calcNegative = false)
		{
			if (trade == null)
				throw new ArgumentNullException("trade");

			if (trade.Order.Type == OrderTypes.Market)
				return 0;

			if (estimatedPrice <= 0)
				throw new ArgumentOutOfRangeException("estimatedPrice", estimatedPrice, "Неправильное значение для ожидаемой цены.");

			var slippage = (trade.Order.Direction == OrderDirections.Buy)
				? trade.Trade.Price - estimatedPrice
				: estimatedPrice - trade.Trade.Price;

			//
			// заявка была кинута глубоко в рынок
			// http://stocksharp.com/forum/default.aspx?g=posts&m=3739#post3739
			//
			if (!calcNegative && slippage < 0)
				slippage = 0;

			return slippage * trade.Trade.Volume;
		}

		/// <summary>
		/// Проверить, является ли переданное время торгуемым (началась ли сессия, не закончилась ли, нет ти клиринга).
		/// </summary>
		/// <param name="timeBounds">Временные интервалы работы биржи. Например, для FORTS можно передавать значения 10:00-13:59, 14:03-18:49 и 19:00-23:49.</param>
		/// <param name="dateTime">Передаваемое время, которое нужно проверить.</param>
		/// <returns>True, если торгуемое время, иначе, неторгуемое.</returns>
		public static bool IsTradeTime(this IEnumerable<Range<TimeSpan>> timeBounds, DateTime dateTime)
		{
			if (timeBounds == null)
				throw new ArgumentNullException("timeBounds");

			return new WorkingTime { Times = timeBounds.ToArray() }.IsTradeTime(dateTime);
		}

		/// <summary>
		/// Проверить, является ли текущее время торгуемым (началась ли сессия, не закончилась ли, нет ти клиринга).
		/// </summary>
		/// <param name="security">Инструмент, хранящий информацию о бирже <see cref="Security.ExchangeBoard"/>. Если она равна null, то инструмент торгуется
		/// круглосуточно. Иначе, информация передается в метод <see cref="IsTradeTime(StockSharp.BusinessEntities.ExchangeBoard,System.DateTime)"/>.</param>
		/// <param name="dateTime">Передаваемое время, которое нужно проверить.</param>
		/// <returns>True, если торгуемое время, иначе, неторгуемое.</returns>
		public static bool IsTradeTime(this Security security, DateTime dateTime)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			return security.ExchangeBoard == null || security.ExchangeBoard.IsTradeTime(dateTime);
		}

		/// <summary>
		/// Проверить, является ли текущее время торгуемым (началась ли сессия, не закончилась ли, нет ти клиринга).
		/// </summary>
		/// <param name="board">Информация о площадке, хранящая временные интервалы работы биржи <see cref="ExchangeBoard.WorkingTime"/>.</param>
		/// <param name="trader">Шлюз с торговой системой. В качестве текущего времени берется результат выполнения метода <see cref="ITrader.GetMarketTime"/>.</param>
		/// <returns>True, если торгуемое время, иначе, неторгуемое.</returns>
		public static bool IsTradeTime(this ExchangeBoard board, ITrader trader)
		{
			if (trader == null)
				throw new ArgumentNullException("trader");

			return board.IsTradeTime(trader.GetMarketTime(board.Exchange));
		}

		/// <summary>
		/// Проверить, является ли текущее время торгуемым (началась ли сессия, не закончилась ли, нет ти клиринга).
		/// </summary>
		/// <param name="board">Информация о площадке, хранящая временные интервалы работы биржи <see cref="ExchangeBoard.WorkingTime"/>.
		/// Например, для FORTS будут значения 10:00-13:59, 14:03-18:49 и 19:00-23:49.</param>
		/// <param name="dateTime">Передаваемое время, которое нужно проверить.</param>
		/// <returns>True, если торгуемое время, иначе, неторгуемое.</returns>
		public static bool IsTradeTime(this ExchangeBoard board, DateTime dateTime)
		{
			if (board == null)
				throw new ArgumentNullException("board");

			return board.WorkingTime.IsTradeTime(dateTime);
		}

		/// <summary>
		/// Проверить, является ли время торгуемым (началась ли сессия, не закончилась ли, нет ли клиринга).
		/// </summary>
		/// <param name="workingTime">Информация о режиме работы биржи.</param>
		/// <param name="dateTime">Передаваемое время, которое нужно проверить.</param>
		/// <returns>True, если торгуемое время, иначе, неторгуемое.</returns>
		public static bool IsTradeTime(this WorkingTime workingTime, DateTime dateTime)
		{
			var isWorkingDay = workingTime.IsTradeDate(dateTime.Date);

			if (!isWorkingDay)
				return false;

			var time = dateTime.TimeOfDay;
			var timeBounds = workingTime.Times;

			return timeBounds.IsEmpty() || timeBounds.Any(r => r.Contains(time));
		}

		/// <summary>
		/// Проверить, является ли дата торгуемой.
		/// </summary>
		/// <param name="workingTime">Информация о режиме работы биржи.</param>
		/// <param name="date">Передаваемая дата, которую необходимо проверить.</param>
		/// <param name="checkHolidays">Проверять ли переданную дату на день недели (суббота и воскресенье являются выходными и для них будет возвращено false).</param>
		/// <returns>True, если торгуемая дата, иначе, неторгуемая.</returns>
		public static bool IsTradeDate(this WorkingTime workingTime, DateTime date, bool checkHolidays = false)
		{
			if (workingTime == null)
				throw new ArgumentNullException("workingTime");

			if (workingTime.Times.Length == 0 && workingTime.SpecialWorkingDays.Length == 0 && workingTime.SpecialHolidays.Length == 0)
				return true;

			bool isWorkingDay;

			if (checkHolidays && (date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday))
				isWorkingDay = workingTime.SpecialWorkingDays.Contains(date.Date);
			else
				isWorkingDay = !workingTime.SpecialHolidays.Contains(date.Date);

			return isWorkingDay;
		}

		/// <summary>
		/// Заполнить <see cref="WorkingTime.SpecialHolidays"/> праздниками по-умолчанию.
		/// </summary>
		/// <param name="board">Площадка.</param>
		/// <param name="startYear">Начальный год.</param>
		/// <param name="endYear">Конечный год.</param>
		public static void ApplyDefaultHolidays(this ExchangeBoard board, DateTime startYear, DateTime endYear)
		{
			if (board == null)
				throw new ArgumentNullException("board");

			if (startYear >= endYear)
				throw new ArgumentOutOfRangeException("endYear");

			var holidays = new List<DateTime>();

			for (var year = startYear.Year; year <= endYear.Year; year++)
			{
				for (var i = 1; i <= 10; i++)
					holidays.Add(new DateTime(year, 1, i));

				holidays.Add(new DateTime(year, 2, 23));
				holidays.Add(new DateTime(year, 3, 8));
				holidays.Add(new DateTime(year, 5, 1));
				holidays.Add(new DateTime(year, 5, 2));
				holidays.Add(new DateTime(year, 5, 9));
				holidays.Add(new DateTime(year, 6, 13));
				holidays.Add(new DateTime(year, 11, 4));
			}

			board.WorkingTime.SpecialHolidays = holidays.ToArray();
		}

		/// <summary>
		/// Получить биржевое время.
		/// </summary>
		/// <param name="security">Инструмент, из которого будет взята информация о бирже <see cref="Security.ExchangeBoard"/>.</param>
		/// <returns>Биржевое время.</returns>
		public static DateTime GetMarketTime(this Security security)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			return security.CheckTrader().GetMarketTime(security.CheckExchangeBoard().Exchange);
		}

		/// <summary>
		/// Создать копию заявки для перерегистрации с новым объемом, равным <see cref="Order.Balance"/> заявки <paramref name="oldOrder"/>.
		/// </summary>
		/// <param name="oldOrder">Оригинальная заявка.</param>
		/// <param name="newPrice">Цена новой заявки.</param>
		/// <returns>Новая заявка.</returns>
		public static Order ReRegisterClone(this Order oldOrder, decimal newPrice)
		{
			return oldOrder.ReRegisterClone(newPrice, oldOrder.Balance);
		}

		/// <summary>
		/// Создать копию заявки для перерегистрации.
		/// </summary>
		/// <param name="oldOrder">Оригинальная заявка.</param>
		/// <param name="newPrice">Цена новой заявки.</param>
		/// <param name="newVolume">Объем новой заявки.</param>
		/// <returns>Новая заявка.</returns>
		public static Order ReRegisterClone(this Order oldOrder, decimal newPrice, decimal newVolume)
		{
			if (oldOrder == null)
				throw new ArgumentNullException("oldOrder");

			var newOrder = oldOrder.Clone();

			newOrder.Price = newPrice;
			newOrder.Volume = newVolume;

			return newOrder;
		}

		private static readonly ChannelFactory<IDailyInfoSoap> _dailyInfoFactory = new ChannelFactory<IDailyInfoSoap>(new BasicHttpBinding(), new EndpointAddress("http://www.cbr.ru/dailyinfowebserv/dailyinfo.asmx"));
		private static readonly Dictionary<DateTime, Dictionary<CurrencyTypes, decimal>> _rateInfo = new Dictionary<DateTime, Dictionary<CurrencyTypes, decimal>>();

		/// <summary>
		/// Сконвертировать одну валюту в другую.
		/// </summary>
		/// <param name="currencyFrom">Валюта, из которой нужно произвести конвертацию.</param>
		/// <param name="currencyTypeTo">Код валюты, в которую нужно произвести конвертацию.</param>
		/// <returns>Сконвертированная валюта.</returns>
		public static Currency Convert(this Currency currencyFrom, CurrencyTypes currencyTypeTo)
		{
			if (currencyFrom == null)
				throw new ArgumentNullException("currencyFrom");

			return new Currency { Type = currencyTypeTo, Value = currencyFrom.Value * currencyFrom.Type.Convert(currencyTypeTo) };
		}

		/// <summary>
		/// Получить курс конвертации одной валюту в другую.
		/// </summary>
		/// <param name="from">Код валюты, из которой нужно произвести конвертацию.</param>
		/// <param name="to">Код валюты, в которую нужно произвести конвертацию.</param>
		/// <returns>Курс.</returns>
		public static decimal Convert(this CurrencyTypes from, CurrencyTypes to)
		{
			return from.Convert(to, DateTime.Today);
		}

		/// <summary>
		/// Получить курс конвертации одной валюту в другую на определенную дату.
		/// </summary>
		/// <param name="from">Код валюты, из которой нужно произвести конвертацию.</param>
		/// <param name="to">Код валюты, в которую нужно произвести конвертацию.</param>
		/// <param name="date">Дата курса.</param>
		/// <returns>Курс.</returns>
		public static decimal Convert(this CurrencyTypes from, CurrencyTypes to, DateTime date)
		{
			if (from == to)
				return 1;

			var info = _rateInfo.SafeAdd(date, key =>
			{
				var i = _dailyInfoFactory.Invoke(c => c.GetCursOnDate(key));
				return i.Tables[0].Rows.Cast<DataRow>().ToDictionary(r => r[4].To<CurrencyTypes>(), r => r[2].To<decimal>());
			});

			if (from != CurrencyTypes.RUB && !info.ContainsKey(from))
				throw new ArgumentException("Валюта '{0}' не поддерживается.".Put(from), "from");

			if (to != CurrencyTypes.RUB && !info.ContainsKey(to))
				throw new ArgumentException("Валюта '{0}' не поддерживается.".Put(to), "to");

			if (from == CurrencyTypes.RUB)
				return 1 / info[to];
			else if (to == CurrencyTypes.RUB)
				return info[from];
			else
				return info[from] / info[to];
		}

		/// <summary>
		/// Создать из обычного стакана разреженный с минимальным шагом цены равный <see cref="Security.MinStepSize"/>.
		/// <remarks>
		/// В разреженном стакане показаны котировки на те цены, по которым не выставлены заявки. Объем таких котировок равен 0.
		/// </remarks>
		/// </summary>
		/// <param name="depth">Обычный стакан.</param>
		/// <returns>Разреженный стакан.</returns>
		public static MarketDepth Sparse(this MarketDepth depth)
		{
			if (depth == null)
				throw new ArgumentNullException("depth");

			return depth.Sparse(depth.Security.MinStepSize);
		}

		/// <summary>
		/// Создать из обычного стакана разреженный.
		/// <remarks>
		/// В разреженном стакане показаны котировки на те цены, по которым не выставлены заявки. Объем таких котировок равен 0.
		/// </remarks>
		/// </summary>
		/// <param name="depth">Обычный стакан.</param>
		/// <param name="minStepSize">Минимальный шаг цены.</param>
		/// <returns>Разреженный стакан.</returns>
		public static MarketDepth Sparse(this MarketDepth depth, decimal minStepSize)
		{
			if (depth == null)
				throw new ArgumentNullException("depth");

			var bids = depth.Bids.Sparse(minStepSize);
			var asks = depth.Asks.Sparse(minStepSize);

			var spreadQuotes = depth.BestPair.Sparse(minStepSize);

			return new MarketDepth(depth.Security).Update(
				bids.Concat(spreadQuotes.Where(q => q.OrderDirection == OrderDirections.Buy)),
				asks.Concat(spreadQuotes.Where(q => q.OrderDirection == OrderDirections.Sell)),
				false, depth.LastChangeTime);
		}

		/// <summary>
		/// Создать из пары котировок разреженную коллекцию котировок, которая будет входить в диапазон между парой.
		/// <remarks>
		/// В разреженной коллекции показаны котировки на те цены, по которым не выставлены заявки. Объем таких котировок равен 0.
		/// </remarks>
		/// </summary>
		/// <param name="pair">Пара обычных котировок.</param>
		/// <param name="minStepSize">Минимальный шаг цены.</param>
		/// <returns>Разреженная коллекция котировок.</returns>
		public static IEnumerable<Quote> Sparse(this MarketDepthPair pair, decimal minStepSize)
		{
			if (pair == null)
				throw new ArgumentNullException("pair");

			if (minStepSize <= 0)
				throw new ArgumentOutOfRangeException("minStepSize", minStepSize, "Размер шага цены меньше допустимого.");

			if (pair.SpreadPrice == 0 && pair.SpreadPrice <= minStepSize)
				return new Quote[0];

			var security = pair.Bid.Security;

			var retVal = new List<Quote>();

			var bidPrice = pair.Bid.Price;
			var askPrice = pair.Ask.Price;

			var minStepDecimal = minStepSize;

			while (true)
			{
				bidPrice += minStepDecimal;
				askPrice -= minStepDecimal;

				if (bidPrice > askPrice)
					break;

				retVal.Add(new Quote
				{
					Security = security,
					Price = bidPrice,
					OrderDirection = OrderDirections.Buy,
				});

				if (bidPrice == askPrice)
					break;

				retVal.Add(new Quote
				{
					Security = security,
					Price = askPrice,
					OrderDirection = OrderDirections.Sell,
				});
			}

			return retVal.OrderBy(q => q.Price);
		}

		/// <summary>
		/// Создать из обычных котировок разреженную коллекцию котировок.
		/// <remarks>
		/// В разреженной коллекции показаны котировки на те цены, по которым не выставлены заявки. Объем таких котировок равен 0.
		/// </remarks>
		/// </summary>
		/// <param name="quotes">Обычные котировки. Коллекция должна содержать одинаково направленные котировки (только биды или только оффера).</param>
		/// <param name="minStepSize">Минимальный шаг цены.</param>
		/// <returns>Разреженная коллекция котировок.</returns>
		public static IEnumerable<Quote> Sparse(this IEnumerable<Quote> quotes, decimal minStepSize)
		{
			if (quotes == null)
				throw new ArgumentNullException("quotes");

			if (minStepSize <= 0)
				throw new ArgumentOutOfRangeException("minStepSize", minStepSize, "Размер шага цены меньше допустимого.");

			var list = quotes.OrderBy(q => q.Price).ToList();

			if (list.Count < 2)
				return new Quote[0];

			var firstQuote = list[0];

			var retVal = new List<Quote>();

			for (var i = 0; i < (list.Count - 1); i++)
			{
				var from = list[i];

				if (from.OrderDirection != firstQuote.OrderDirection)
					throw new ArgumentException("Коллекция котировок не одинаково направлена.", "quotes");

				var toPrice = list[i + 1].Price;

				for (var price = (from.Price + minStepSize); price < toPrice; price += minStepSize)
				{
					retVal.Add(new Quote
					{
						Security = firstQuote.Security,
						Price = price,
						OrderDirection = firstQuote.OrderDirection,
					});
				}
			}

			if (firstQuote.OrderDirection == OrderDirections.Buy)
				return retVal.OrderByDescending(q => q.Price);
			else
				return retVal;
		}

		/// <summary>
		/// Объединить первоначальный стакан, и его разреженное представление.
		/// </summary>
		/// <param name="original">Первоначальный стакан.</param>
		/// <param name="rare">Разреженный стакан.</param>
		/// <returns>Объединенный стакан.</returns>
		public static MarketDepth Join(this MarketDepth original, MarketDepth rare)
		{
			if (original == null)
				throw new ArgumentNullException("original");

			if (rare == null)
				throw new ArgumentNullException("rare");

			return new MarketDepth(original.Security).Update(original.Concat(rare), original.LastChangeTime);
		}

		/// <summary>
		/// Сгруппировать стакан по ценовому диапазону.
		/// </summary>
		/// <param name="depth">Стакан, который необходимо сгруппировать.</param>
		/// <param name="priceRange">Ценовой диапазон, по которому необходимо произвести группировку.</param>
		/// <returns>Сгруппированный стакан.</returns>
		public static MarketDepth Group(this MarketDepth depth, Unit priceRange)
		{
			return new MarketDepth(depth.Security).Update(depth.Bids.Group(priceRange), depth.Asks.Group(priceRange), true, depth.LastChangeTime);
		}

		/// <summary>
		/// Разгруппировать стакан, сгруппированный через метод <see cref="Group(StockSharp.BusinessEntities.MarketDepth,StockSharp.BusinessEntities.Unit)"/>.
		/// </summary>
		/// <param name="depth">Сгруппированный стакан.</param>
		/// <returns>Разгруппированный стакан.</returns>
		public static MarketDepth UnGroup(this MarketDepth depth)
		{
			return new MarketDepth(depth.Security).Update(
				depth.Bids.Cast<AggregatedQuote>().SelectMany(gq => gq.InnerQuotes),
				depth.Asks.Cast<AggregatedQuote>().SelectMany(gq => gq.InnerQuotes),
				false, depth.LastChangeTime);
		}

		/// <summary>
		/// Удалить в стакане те уровни, которые должны исчезнуть в случае появления сделок <paramref name="trades"/>.
		/// </summary>
		/// <param name="depth">Стакан, который необходимо очистить.</param>
		/// <param name="trades">Сделки.</param>
		public static void EmulateTrades(this MarketDepth depth, IEnumerable<Trade> trades)
		{
			if (depth == null)
				throw new ArgumentNullException("depth");

			if (trades == null)
				throw new ArgumentNullException("trades");

			var changedVolume = new Dictionary<decimal, decimal>();

			var maxTradePrice = decimal.MinValue;
			var minTradePrice = decimal.MaxValue;

			foreach (var trade in trades)
			{
				minTradePrice = minTradePrice.Min(trade.Price);
				maxTradePrice = maxTradePrice.Max(trade.Price);

				var quote = depth.GetQuote(trade.Price);

				if (null == quote)
					continue;

				decimal vol;
				if (!changedVolume.TryGetValue(trade.Price, out vol))
					vol = quote.Volume;

				vol -= trade.Volume;
				changedVolume[quote.Price] = vol;
			}

			var bids = new Quote[depth.Bids.Length];
			Action a1 = () =>
			{
				var i = 0;
				var count = 0;

				for (; i < depth.Bids.Length; i++)
				{
					var quote = depth.Bids[i];
					var price = quote.Price;

					if (price > minTradePrice)
						continue;

					if (price == minTradePrice)
					{
						decimal vol;
						if (changedVolume.TryGetValue(price, out vol))
						{
							if (vol <= 0)
								continue;

							quote = quote.Clone();
							quote.Volume = vol;
						}
					}

					bids[count++] = quote;
					i++;

					break;
				}

				Array.Copy(depth.Bids, i, bids, count, depth.Bids.Length - i);
				Array.Resize(ref bids, count + (depth.Bids.Length - i));
			};

			a1();

			var asks = new Quote[depth.Asks.Length];
			Action a2 = () =>
			{
				var i = 0;
				var count = 0;

				for (; i < depth.Asks.Length; i++)
				{
					var quote = depth.Asks[i];
					var price = quote.Price;

					if (price < maxTradePrice)
						continue;

					if (price == maxTradePrice)
					{
						decimal vol;
						if (changedVolume.TryGetValue(price, out vol))
						{
							if (vol <= 0)
								continue;

							quote = quote.Clone();
							quote.Volume = vol;
						}
					}

					asks[count++] = quote;
					i++;

					break;
				}

				Array.Copy(depth.Asks, i, asks, count, depth.Asks.Length - i);
				Array.Resize(ref asks, count + (depth.Asks.Length - i));
			};

			a2();

			depth.Update(bids, asks, depth.LastChangeTime);
		}

		/// <summary>
		/// Сгруппировать котировки по ценовому диапазону.
		/// </summary>
		/// <param name="quotes">Котировки, которые необходимо сгруппировать.</param>
		/// <param name="priceRange">Ценовой диапазон, по которому необходимо произвести группировку.</param>
		/// <returns>Сгруппированные котировки.</returns>
		public static IEnumerable<AggregatedQuote> Group(this IEnumerable<Quote> quotes, Unit priceRange)
		{
			if (quotes == null)
				throw new ArgumentNullException("quotes");

			if (priceRange == null)
				throw new ArgumentNullException("priceRange");

			//if (priceRange.Value < double.Epsilon)
			//	throw new ArgumentOutOfRangeException("priceRange", priceRange, "Размер группировки меньше допустимого.");

			if (quotes.Count() < 2)
				return new AggregatedQuote[0];

			var firstQuote = quotes.First();

			var retVal = quotes.GroupBy(q => priceRange.AlignPrice(firstQuote.Price, q.Price)).Select(g =>
			{
				var aggQuote = new AggregatedQuote { Price = g.Key };
				aggQuote.InnerQuotes.AddRange(g);
				return aggQuote;
			});

			retVal = firstQuote.OrderDirection == OrderDirections.Buy ? retVal.OrderBy(q => q.Price) : retVal.OrderByDescending(q => q.Price);

			return retVal;
		}

		internal static decimal AlignPrice(this Unit priceRange, decimal firstPrice, decimal price)
		{
			if (priceRange == null)
				throw new ArgumentNullException("priceRange");

			decimal priceLevel;

			if (priceRange.Type == UnitTypes.Percent)
				priceLevel = (decimal)(firstPrice + MathHelper.Floor((((price - firstPrice) * 100) / firstPrice), priceRange.Value).Percents());
			else
				priceLevel = MathHelper.Floor(price, (decimal)priceRange);

			return priceLevel;
		}

		/// <summary>
		/// Вычислить изменение между стаканами.
		/// </summary>
		/// <param name="from">Первый стакан.</param>
		/// <param name="to">Второй стакан.</param>
		/// <returns>Стакан, хранищий только изменения.</returns>
		public static MarketDepth GetDelta(this MarketDepth from, MarketDepth to)
		{
			if (from == null)
				throw new ArgumentNullException("from");

			if (to == null)
				throw new ArgumentNullException("to");

			var delta = new MarketDepth(from.Security);
			delta.Update(GetDelta(from.Bids, to.Bids), GetDelta(from.Asks, to.Asks), false, to.LastChangeTime);
			return delta;
		}

		/// <summary>
		/// Вычислить изменение между котировками.
		/// </summary>
		/// <param name="from">Первые котировки.</param>
		/// <param name="to">Вторые котировки.</param>
		/// <returns>Изменения.</returns>
		public static Quote[] GetDelta(IEnumerable<Quote> from, IEnumerable<Quote> to)
		{
			var mapTo = to.ToDictionary(q => q.Price);
			var mapFrom = from.ToDictionary(q => q.Price);

			foreach (var pair in mapFrom)
			{
				var price = pair.Key;
				var quoteFrom = pair.Value;

				var quoteTo = mapTo.TryGetValue(price);

				if (quoteTo != null)
				{
					if (quoteTo.Volume == quoteFrom.Volume)
						mapTo.Remove(price);		// то же самое
				}
				else
				{
					var empty = quoteFrom.Clone();
					empty.Volume = 0;				// была а теперь нет
					mapTo[price] = empty;
				}
			}

			return mapTo.Values.ToArray();
		}

		/// <summary>
		/// Вычислить приращение между стаканами.
		/// </summary>
		/// <param name="from">Первый стакан.</param>
		/// <param name="to">Второй стакан.</param>
		/// <returns>Стакан, хранищий только приращения.</returns>
		public static MarketDepth GetDiff(this MarketDepth from, MarketDepth to)
		{
			if (from == null)
				throw new ArgumentNullException("from");

			if (to == null)
				throw new ArgumentNullException("to");

			var diff = new MarketDepth(from.Security);
			diff.Update(GetDiff(from.Bids, to.Bids), GetDiff(from.Asks, to.Asks), false, to.LastChangeTime);
			return diff;
		}
		/// <summary>
		/// Вычислить приращение между котировками. 
		/// </summary>
		/// <param name="from">Первые котировки.</param>
		/// <param name="to">Вторые котировки.</param>
		/// <returns>Изменения.</returns>
		public static Quote[] GetDiff(IEnumerable<Quote> from, IEnumerable<Quote> to)
		{
			var mapTo = to.ToDictionary(q => q.Price);
			var mapFrom = from.ToDictionary(q => q.Price);

			foreach (var pair in mapFrom)
			{
				var price = pair.Key;
				var quoteFrom = pair.Value;

				var quoteTo = mapTo.TryGetValue(price);

				if (quoteTo != null)
				{
					if (quoteTo.Volume == quoteFrom.Volume)
						mapTo.Remove(price);		// то же самое
					else
					{
						var d = quoteTo.Clone();
						d.Volume -= quoteFrom.Volume;
						mapTo[price] = d;
					}
				}
				else
				{
					var d = quoteFrom.Clone();
					d.Volume = - quoteFrom.Volume;				// была а теперь нет
					mapTo[price] = d;
				}
			}

			return mapTo.Values.ToArray();
		}

		/// <summary>
		/// Прибавить изменение к первому стакану.
		/// </summary>
		/// <param name="from">Первый стакан.</param>
		/// <param name="delta">Изменение.</param>
		/// <returns>Измененный стакан.</returns>
		public static MarketDepth AddDelta(this MarketDepth from, MarketDepth delta)
		{
			if (from == null)
				throw new ArgumentNullException("from");

			if (delta == null)
				throw new ArgumentNullException("delta");

			var depth = new MarketDepth(from.Security);
			depth.Update(
				AddDelta(from.Bids, delta.Bids, true),
				AddDelta(from.Asks, delta.Asks, false),
				delta.LastChangeTime);
			return depth;
		}

		/// <summary>
		/// Прибавить изменение к котировки.
		/// </summary>
		/// <param name="from">Котировки.</param>
		/// <param name="delta">Изменения.</param>
		/// <param name="isBids">Признак направления котировок.</param>
		/// <returns>Измененные котировки.</returns>
		public static Quote[] AddDelta(Quote[] from, Quote[] delta, bool isBids)
		{
			if (from.Length == 0)
				return delta;

			if (delta.Length == 0)
				return from;

			var result = new Quote[from.Length + delta.Length];

			var fromIndex = 0;
			var deltaIndex = 0;
			var outIndex = 0;

			var currFrom = from[fromIndex];
			var currDelta = delta[deltaIndex];
			var currFromPrice = currFrom.Price;
			var currDeltaPrice = currDelta.Price;

			while (true)
			{
				var cmp = Decimal.Compare(currFromPrice, currDeltaPrice);

				if (cmp < 0 && isBids || cmp > 0 && !isBids) //currDeltaPrice > currFromPrice
				{
					result[outIndex++] = currDelta;
					deltaIndex++;

					if (deltaIndex < delta.Length)
					{
						currDelta = delta[deltaIndex];
						currDeltaPrice = currDelta.Price;
					}
					else
						break;
				}
				else if (cmp < 0 && !isBids || cmp > 0 && isBids)
				{
					result[outIndex++] = currFrom;
					fromIndex++;

					if (fromIndex < from.Length)
					{
						currFrom = from[fromIndex];
						currFromPrice = currFrom.Price;
					}
					else
						break;
				}
				else // cmp==0
				{
					if (currDelta.Volume != 0)
						result[outIndex++] = currDelta;

					deltaIndex++;
					fromIndex++;

					if (deltaIndex < delta.Length)
					{
						currDelta = delta[deltaIndex];
						currDeltaPrice = currDelta.Price;
					}
					else
						break;
					if (fromIndex < from.Length)
					{
						currFrom = from[fromIndex];
						currFromPrice = currFrom.Price;
					}
					else
						break;
				}
			}

			Array.Copy(from, fromIndex, result, outIndex, from.Length - fromIndex);
			outIndex += from.Length - fromIndex;

			Array.Copy(delta, deltaIndex, result, outIndex, delta.Length - deltaIndex);
			outIndex += delta.Length - deltaIndex;

			Array.Resize(ref result, outIndex);

			return result;
		}

		/// <summary>
		/// Проверить, отменена ли заявка.
		/// </summary>
		/// <param name="order">Заявка, которую необходимо проверить.</param>
		/// <returns>True, если заявка отменена, иначе, false.</returns>
		public static bool IsCanceled(this Order order)
		{
			if (order == null)
				throw new ArgumentNullException("order");
			
			if (order.State != OrderStates.Done)	// для ускорения в эмуляторе
				return false;

			using (order.BeginRead())
				return (order.State == OrderStates.Done && order.Balance != 0);
		}

		/// <summary>
		/// Проверить, исполнена ли полностью заявка.
		/// </summary>
		/// <param name="order">Заявка, которую необходимо проверить.</param>
		/// <returns>True, если заявка полностью исполнена, иначе, false.</returns>
		public static bool IsMatched(this Order order)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			using (order.BeginRead())
				return (order.State == OrderStates.Done && order.Balance == 0);
		}

		/// <summary>
		/// Проверить, реализована ли часть объема в заявке.
		/// </summary>
		/// <param name="order">Заявка, которую необходимо проверить.</param>
		/// <returns>True, если часть объема реализована, иначе, false.</returns>
		public static bool IsMatchedPartially(this Order order)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			using (order.BeginRead())
				return (order.Balance > 0 && order.Balance != order.Volume);
		}

		/// <summary>
		/// Проверить, что не реализован ни один контракт в заявке.
		/// </summary>
		/// <param name="order">Заявка, которую необходимо проверить.</param>
		/// <returns>True, если ни один контракт не реализована, иначе, false.</returns>
		public static bool IsMatchedEmpty(this Order order)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			using (order.BeginRead())
				return (order.Balance > 0 && order.Balance == order.Volume);
		}

		/// <summary>
		/// Получить сделки заявки.
		/// </summary>
		/// <param name="order">Заявки.</param>
		/// <returns>Сделки.</returns>
		public static IEnumerable<MyTrade> GetTrades(this Order order)
		{
			return order.CheckTrader().MyTrades.Filter(order);
		}

		/// <summary>
		/// Расcчитать реализованную часть объема для заявки.
		/// </summary>
		/// <param name="order">Заявка, для которой необходимо расcчитать реализованную часть объема.</param>
		/// <param name="byOrder">Проверять реализованный объем по балансу заявке (<see cref="Order.Balance"/>) или по полученным сделкам.
		/// По-умолчанию проверяется по заявке.</param>
		/// <returns>Реализованная часть объема.</returns>
		public static decimal GetMatchedVolume(this Order order, bool byOrder = true)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			if (order.Type == OrderTypes.Conditional)
			{
				//throw new ArgumentException("Стоп-заявки не могут иметь реализованный объем.", "order");

				order = order.DerivedOrder;

				if (order == null)
					return 0;
			}

			return order.Volume - (byOrder ? order.Balance : order.GetTrades().Sum(o => o.Trade.Volume));
		}

		/// <summary>
		/// Получить средневзрешанную цену исполнения заявки.
		/// </summary>
		/// <param name="order">Заявка, для которой необходимо получить средневзрешанную цену исполнения.</param>
		/// <returns>Средневзвешанная цена. Если заявка не существует ни одной сделки, то возвращается 0.</returns>
		public static decimal GetAveragePrice(this Order order)
		{
			return order.GetTrades().GetAveragePrice();
		}

		/// <summary>
		/// Получить средневзрешанную цену исполнения по собственным сделкам.
		/// </summary>
		/// <param name="trades">Сделки, для которых необходимо получить средневзрешанную цену исполнения.</param>
		/// <returns>Средневзвешанная цена. Если сделки отсутствуют, то возвращается 0.</returns>
		public static decimal GetAveragePrice(this IEnumerable<MyTrade> trades)
		{
			if (trades == null)
				throw new ArgumentNullException("trades");

			return trades.Select(t => t.Trade).GetAveragePrice();
		}

		/// <summary>
		/// Получить средневзрешанную цену исполнения по тиковым сделкам.
		/// </summary>
		/// <param name="trades">Сделки, для которых необходимо получить средневзрешанную цену исполнения.</param>
		/// <returns>Средневзвешанная цена. Если сделки отсутствуют, то возвращается 0.</returns>
		public static decimal GetAveragePrice(this IEnumerable<Trade> trades)
		{
			if (trades == null)
				throw new ArgumentNullException("trades");

			var nominator = 0m;
			var denominator = 0m;

			foreach (var trade in trades)
			{
				nominator += trade.Price * trade.Volume;
				denominator += trade.Volume;
			}

			if (denominator == 0)
				return 0;

			return nominator / denominator;
		}

		/// <summary>
		/// Получить вероятные сделки по стакану для заданной заявки.
		/// </summary>
		/// <param name="depth">Стакан, который в момент вызова функции отражает ситуацию на рынке.</param>
		/// <param name="order">Заявку, для которой необходимо расcчитать вероятные сделки.</param>
		/// <returns>Вероятные сделки.</returns>
		public static IEnumerable<MyTrade> GetTheoreticalTrades(this MarketDepth depth, Order order)
		{
			if (depth == null)
				throw new ArgumentNullException("depth");

			if (depth.Trader == null)
				throw new ArgumentException("Стакан не имеет информацию о шлюзе.", "depth");

			if (order == null)
				throw new ArgumentNullException("order");

			using (var emulator = new MarketEmulator(depth.Trader))
			{
				emulator.Reset(LoggingHelper.Now);
				emulator.Start();
				emulator.RegisterOrder(order.Clone());
				emulator.UpdateQuotes(depth.Clone(), null);

				return emulator.MyTrades;
			}
		}

		/// <summary>
		/// Получить вероятные сделки по стакану для рыночной цены и заданного объема.
		/// </summary>
		/// <param name="depth">Стакан, который в момент вызова функции отражает ситуацию на рынке.</param>
		/// <param name="orderDirection">Направление заявки.</param>
		/// <param name="volume">Объем, который предполагается реализовать.</param>
		/// <returns>Вероятные сделки.</returns>
		public static IEnumerable<MyTrade> GetTheoreticalTrades(this MarketDepth depth, OrderDirections orderDirection, decimal volume)
		{
			return depth.GetTheoreticalTrades(orderDirection, volume, 0);
		}

		private static readonly Portfolio _testPf = new Portfolio { Name = "test account" };

		/// <summary>
		/// Получить вероятные сделки по стакану для заданных цены и объема.
		/// </summary>
		/// <param name="depth">Стакан, который в момент вызова функции отражает ситуацию на рынке.</param>
		/// <param name="orderDirection">Направление заявки.</param>
		/// <param name="volume">Объем, который предполагается реализовать.</param>
		/// <param name="price">Цена, по которой предполагает выставить заявку. Если она равна 0, то будет рассматриваться вариант рыночной заявки.</param>
		/// <returns>Вероятные сделки.</returns>
		public static IEnumerable<MyTrade> GetTheoreticalTrades(this MarketDepth depth, OrderDirections orderDirection, decimal volume, decimal price)
		{
			if (depth == null)
				throw new ArgumentNullException("depth");

			return depth.GetTheoreticalTrades(new Order
			{
				Direction = orderDirection,
				Type = price == 0 ? OrderTypes.Market : OrderTypes.Limit,
				Security = depth.Security,
				Price = price,
				Volume = volume,
				Portfolio = _testPf
			});
		}

		/// <summary>
		/// Поменять направление заявки на противоположное.
		/// </summary>
		/// <param name="direction">Первоначальное направление.</param>
		/// <returns>Противоположное направление.</returns>
		public static OrderDirections Invert(this OrderDirections direction)
		{
			return direction == OrderDirections.Buy ? OrderDirections.Sell : OrderDirections.Buy;
		}

		/// <summary>
		/// Получить направление заявки для позиции.
		/// </summary>
		/// <remarks>
		/// Положительное значение равно <see cref="OrderDirections.Buy"/>, отрицательное - <see cref="OrderDirections.Sell"/>, нулевое - null.
		/// </remarks>
		/// <param name="position">Значение позиции.</param>
		/// <returns>Направление заявки.</returns>
		public static OrderDirections? GetDirection(this Position position)
		{
			if (position == null)
				throw new ArgumentNullException("position");

			return position.CurrentValue.GetDirection();
		}

		/// <summary>
		/// Получить направление заявки для позиции.
		/// </summary>
		/// <remarks>
		/// Положительное значение равно <see cref="OrderDirections.Buy"/>, отрицательное - <see cref="OrderDirections.Sell"/>, нулевое - null.
		/// </remarks>
		/// <param name="position">Значение позиции.</param>
		/// <returns>Направление заявки.</returns>
		public static OrderDirections? GetDirection(this decimal position)
		{
			if (position == 0)
				return null;

			return position > 0 ? OrderDirections.Buy : OrderDirections.Sell;
		}

		/// <summary>
		/// Отменить группу заявок на бирже по фильтру.
		/// </summary>
		/// <param name="trader">Шлюз взаимодействия с торговыми системами.</param>
		/// <param name="orders">Группа заявок, из которой необходимо найти требуемые заявки и отменить их.</param>
		/// <param name="isStopOrder">True, если нужно отменить только стоп-заявки, false - если только обычный и null - если оба типа.</param>
		/// <param name="portfolio">Портфель. Если значение равно null, то портфель не попадает в фильтр снятия заявок.</param>
		/// <param name="direction">Направление заявки. Если значение равно null, то направление не попадает в фильтр снятия заявок.</param>
		/// <param name="classCode">Код класса. Если переданная строка пустая, то код не попадает в фильтр снятия заявок.</param>
		/// <param name="security">Инструмент. Если значение равно null, то инструмент не попадает в фильтр снятия заявок.</param>
		public static void CancelOrders(this ITrader trader, IEnumerable<Order> orders, bool? isStopOrder = null, Portfolio portfolio = null, OrderDirections? direction = null, string classCode = null, Security security = null)
		{
			if (trader == null)
				throw new ArgumentNullException("trader");

			if (orders == null)
				throw new ArgumentNullException("orders");

			foreach (var order in orders.Where(o => o.State != OrderStates.Done).ToArray())
			{
				if (isStopOrder == null || (order.Type == OrderTypes.Conditional) == isStopOrder)
				{
					if (portfolio == null || (order.Portfolio == portfolio))
					{
						if (direction == null || order.Direction == direction)
						{
							if (classCode.IsEmpty() || order.Security.Class == classCode)
							{
								if (security == null || order.Security == security)
								{
									trader.CancelOrder(order);
								}
							}
						}
					}
				}
			}
		}

		/// <summary>
		/// Отфильтровать заявки для заданного инструмента.
		/// </summary>
		/// <param name="orders">Все заявки, в которых необходимо искать требуемые.</param>
		/// <param name="security">Инструмент, для которого нужно отфильтровать заявки.</param>
		/// <returns>Отфильтрованные заявки.</returns>
		public static IEnumerable<Order> Filter(this IEnumerable<Order> orders, Security security)
		{
			if (orders == null)
				throw new ArgumentNullException("orders");

			if (security == null)
				throw new ArgumentNullException("security");

			var basket = security as BasketSecurity;
			return basket == null ? orders.Where(o => o.Security == security) : basket.InnerSecurities.SelectMany(s => Filter(orders, s));
		}

		/// <summary>
		/// Отфильтровать заявки для заданного портфеля.
		/// </summary>
		/// <param name="orders">Все заявки, в которых необходимо искать требуемые.</param>
		/// <param name="portfolio">Портфель, для которого нужно отфильтровать заявки.</param>
		/// <returns>Отфильтрованные заявки.</returns>
		public static IEnumerable<Order> Filter(this IEnumerable<Order> orders, Portfolio portfolio)
		{
			if (orders == null)
				throw new ArgumentNullException("orders");

			if (portfolio == null)
				throw new ArgumentNullException("portfolio");

			return orders.Where(p => p.Portfolio == portfolio);
		}

		/// <summary>
		/// Отфильтровать заявки для заданного состояния.
		/// </summary>
		/// <param name="orders">Все заявки, в которых необходимо искать требуемые.</param>
		/// <param name="state">Состояние заявки.</param>
		/// <returns>Отфильтрованные заявки.</returns>
		public static IEnumerable<Order> Filter(this IEnumerable<Order> orders, OrderStates state)
		{
			if (orders == null)
				throw new ArgumentNullException("orders");

			return orders.Where(p => p.State == state);
		}

		/// <summary>
		/// Отфильтровать заявки для заданного направления.
		/// </summary>
		/// <param name="orders">Все заявки, в которых необходимо искать требуемые.</param>
		/// <param name="direction">Направление заявки.</param>
		/// <returns>Отфильтрованные заявки.</returns>
		public static IEnumerable<Order> Filter(this IEnumerable<Order> orders, OrderDirections direction)
		{
			if (orders == null)
				throw new ArgumentNullException("orders");

			return orders.Where(p => p.Direction == direction);
		}

		/// <summary>
		/// Отфильтровать сделки для заданного инструмента.
		/// </summary>
		/// <param name="trades">Все сделки, в которых необходимо искать требуемые.</param>
		/// <param name="security">Инструмент, для которого нужно отфильтровать сделки.</param>
		/// <returns>Отфильтрованные сделки.</returns>
		public static IEnumerable<Trade> Filter(this IEnumerable<Trade> trades, Security security)
		{
			if (trades == null)
				throw new ArgumentNullException("trades");

			if (security == null)
				throw new ArgumentNullException("security");

			var basket = security as BasketSecurity;
			return basket == null ? trades.Where(t => t.Security == security) : basket.InnerSecurities.SelectMany(s => Filter(trades, s));
		}

		/// <summary>
		/// Отфильтровать сделки для заданного временного периода.
		/// </summary>
		/// <param name="trades">Все сделки, в которых необходимо искать требуемые.</param>
		/// <param name="from">Дата, с которой нужно искать сделки.</param>
		/// <param name="to">Дата, до которой нужно искать сделки.</param>
		/// <returns>Отфильтрованные сделки.</returns>
		public static IEnumerable<Trade> Filter(this IEnumerable<Trade> trades, DateTime from, DateTime to)
		{
			if (trades == null)
				throw new ArgumentNullException("trades");

			return trades.Where(trade => trade.Time >= from && trade.Time < to);
		}

		/// <summary>
		/// Отфильтровать позиции для заданного инструмента.
		/// </summary>
		/// <param name="positions">Все позиции, в которых необходимо искать требуемые.</param>
		/// <param name="security">Инструмент, для которого нужно отфильтровать позиции.</param>
		/// <returns>Отфильтрованные позиции.</returns>
		public static IEnumerable<Position> Filter(this IEnumerable<Position> positions, Security security)
		{
			if (positions == null)
				throw new ArgumentNullException("positions");

			if (security == null)
				throw new ArgumentNullException("security");

			var basket = security as BasketSecurity;
			return basket == null ? positions.Where(p => p.Security == security) : basket.InnerSecurities.SelectMany(s => Filter(positions, s));
		}

		/// <summary>
		/// Отфильтровать позиции для заданного портфеля.
		/// </summary>
		/// <param name="positions">Все позиции, в которых необходимо искать требуемые.</param>
		/// <param name="portfolio">Портфель, для которого нужно отфильтровать позиции.</param>
		/// <returns>Отфильтрованные позиции.</returns>
		public static IEnumerable<Position> Filter(this IEnumerable<Position> positions, Portfolio portfolio)
		{
			if (positions == null)
				throw new ArgumentNullException("positions");

			if (portfolio == null)
				throw new ArgumentNullException("portfolio");

			return positions.Where(p => p.Portfolio == portfolio);
		}

		/// <summary>
		/// Отфильтровать собственные сделки для заданного инструмента.
		/// </summary>
		/// <param name="myTrades">Все собственные сделки, в которых необходимо искать требуемые.</param>
		/// <param name="security">Инструмент, по которому нужно найти сделки.</param>
		/// <returns>Отфильтрованные сделки.</returns>
		public static IEnumerable<MyTrade> Filter(this IEnumerable<MyTrade> myTrades, Security security)
		{
			if (myTrades == null)
				throw new ArgumentNullException("myTrades");

			if (security == null)
				throw new ArgumentNullException("security");

			var basket = security as BasketSecurity;
			return basket == null ? myTrades.Where(t => t.Order.Security == security) : basket.InnerSecurities.SelectMany(s => Filter(myTrades, s));
		}

		/// <summary>
		/// Отфильтровать собственные сделки для заданного портфеля.
		/// </summary>
		/// <param name="myTrades">Все собственные сделки, в которых необходимо искать требуемые.</param>
		/// <param name="portfolio">Портфель, для которого нужно отфильтровать сделки.</param>
		/// <returns>Отфильтрованные сделки.</returns>
		public static IEnumerable<MyTrade> Filter(this IEnumerable<MyTrade> myTrades, Portfolio portfolio)
		{
			if (myTrades == null)
				throw new ArgumentNullException("myTrades");

			if (portfolio == null)
				throw new ArgumentNullException("portfolio");

			return myTrades.Where(t => t.Order.Portfolio == portfolio);
		}

		/// <summary>
		/// Отфильтровать собственные сделки для заданной заявки.
		/// </summary>
		/// <param name="myTrades">Все собственные сделки, в которых необходимо искать требуемые.</param>
		/// <param name="order">Заявка, для которой нужно отфильтровать сделки.</param>
		/// <returns>Отфильтрованные заявки.</returns>
		public static IEnumerable<MyTrade> Filter(this IEnumerable<MyTrade> myTrades, Order order)
		{
			if (myTrades == null)
				throw new ArgumentNullException("myTrades");

			if (order == null)
				throw new ArgumentNullException("order");

			return myTrades.Where(t => t.Order == order);
		}

		/// <summary>
		/// Определить, является ли стакан пустым.
		/// </summary>
		/// <param name="depth">Стакан.</param>
		/// <returns>True, если стакан пустой, иначе, false.</returns>
		public static bool IsFullEmpty(this MarketDepth depth)
		{
			if (depth == null)
				throw new ArgumentNullException("depth");

			return depth.Bids.Length ==0 && depth.Asks.Length == 0;
		}

		/// <summary>
		/// Определить, является ли стакан пустым на половину.
		/// </summary>
		/// <param name="depth">Стакан.</param>
		/// <returns>True, если стакан пустой на половину, иначе, false.</returns>
		public static bool IsHalfEmpty(this MarketDepth depth)
		{
			if (depth == null)
				throw new ArgumentNullException("depth");

			return (depth.BestPair.Bid == null || depth.BestPair.Ask == null) && (depth.BestPair.Bid != depth.BestPair.Ask);
		}

		/// <summary>
		/// Получить T+N дату.
		/// </summary>
		/// <param name="time">Информация о режиме работы биржи.</param>
		/// <param name="date">Начальная дата T.</param>
		/// <param name="n">Размер N.</param>
		/// <returns>Конечная дата T+N.</returns>
		public static DateTime GetTPlusNDate(this WorkingTime time, DateTime date, int n)
		{
			if (time == null)
				throw new ArgumentNullException("time");

			date = date.Date;

			while (n > 0)
			{
				if (time.IsTradeDate(date))
					n--;

				date = date.AddDays(1);
			}

			return date;
		}

		/// <summary>
		/// Перевести локальное время в биржевое.
		/// </summary>
		/// <param name="exchange">Информация о бирже.</param>
		/// <param name="time">Локальное время.</param>
		/// <returns>Время с биржевым сдвигом.</returns>
		public static DateTime ToExchangeTime(this Exchange exchange, DateTime time)
		{
			return exchange.ToExchangeTime(time, TimeZoneInfo.Local);
		}

		/// <summary>
		/// Перевести локальное время в биржевое.
		/// </summary>
		/// <param name="exchange">Информация о бирже.</param>
		/// <param name="time">Локальное время.</param>
		/// <param name="sourceZone">Времемнная зона, в которой записано значение <paramref name="time"/>.</param>
		/// <returns>Время с биржевым сдвигом.</returns>
		public static DateTime ToExchangeTime(this Exchange exchange, DateTime time, TimeZoneInfo sourceZone)
		{
			if (exchange == null)
				throw new ArgumentNullException("exchange");

			return TimeZoneInfo.ConvertTime(time, sourceZone, exchange.TimeZoneInfo);
		}

		/// <summary>
		/// Перевести биржевое время в локальное.
		/// </summary>
		/// <param name="exchange">Информация о бирже, из которой будет использоваться <see cref="Exchange.TimeZoneInfo"/>.</param>
		/// <param name="exchangeTime">Биржевое время.</param>
		/// <returns>Локальное время.</returns>
		public static DateTime ToLocalTime(this Exchange exchange, DateTime exchangeTime)
		{
			if (exchange == null)
				throw new ArgumentNullException("exchange");

			return TimeZoneInfo.ConvertTime(exchangeTime, exchange.TimeZoneInfo, TimeZoneInfo.Local);
		}

		/// <summary>
		/// Перевести биржевое время в UTC.
		/// </summary>
		/// <param name="exchange">Информация о бирже, из которой будет использоваться <see cref="Exchange.TimeZoneInfo"/>.</param>
		/// <param name="exchangeTime">Биржевое время.</param>
		/// <returns>Биржевое время в UTC.</returns>
		public static DateTime ToUtc(this Exchange exchange, DateTime exchangeTime)
		{
			if (exchange == null)
				throw new ArgumentNullException("exchange");

			return TimeZoneInfo.ConvertTimeToUtc(exchangeTime, exchange.TimeZoneInfo);
		}

		/// <summary>
		/// Инициализировать <see cref="MarketDepth.Latency"/>. Задержка равна разнице между <see cref="MarketDepth.LastChangeTime"/> и <see cref="LoggingHelper.Now"/>.
		/// </summary>
		/// <param name="depth">Стакан.</param>
		public static MarketDepth InitLatency(this MarketDepth depth)
		{
			if (depth == null)
				throw new ArgumentNullException("depth");

			depth.Latency = depth.LastChangeTime.GetLatency(depth.Security);
			return depth;
		}

		/// <summary>
		/// Инициализировать <see cref="Trade.Latency"/>. Задержка равна разнице между <see cref="Trade.Time"/> и <see cref="LoggingHelper.Now"/>.
		/// </summary>
		/// <param name="trade">Тиковая сделка.</param>
		public static Trade InitLatency(this Trade trade)
		{
			if (trade == null)
				throw new ArgumentNullException("trade");

			trade.Latency = trade.Time.GetLatency(trade.Security);
			return trade;
		}

		/// <summary>
		/// Инициализировать <see cref="MyTrade.Latency"/>. Задержка равна разнице между <see cref="Trade.Time"/> и <see cref="LoggingHelper.Now"/>.
		/// </summary>
		/// <param name="trade">Собственная сделка.</param>
		public static MyTrade InitLatency(this MyTrade trade)
		{
			if (trade == null)
				throw new ArgumentNullException("trade");

			trade.Latency = (trade is OrderLogItem ? trade.Order.Time : trade.Trade.Time).GetLatency(trade.Order.Security);
			return trade;
		}

		private static TimeSpan GetLatency(this DateTime time, Security security)
		{
			return GetLatency(LoggingHelper.Now, time, security.CheckExchangeBoard().Exchange);
		}

		/// <summary>
		/// Рассчитать задержку.
		/// </summary>
		/// <param name="now">Текущее время.</param>
		/// <param name="time">Биржевое время.</param>
		/// <param name="exchange">Информация о бирже.</param>
		/// <returns>Задержка.</returns>
		public static TimeSpan GetLatency(DateTime now, DateTime time, Exchange exchange)
		{
			return now - exchange.ToLocalTime(time);
		}

		/// <summary>
		/// Получить цену, которую необходимо оплатить для совершения торговой операции.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <param name="direction">Направление торговой операции.</param>
		/// <returns>Цена.</returns>
		public static decimal GetMarginPrice(this Security security, OrderDirections? direction = null)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			if (direction == null)
				return security.LastTrade != null ? security.LastTrade.Price : 0;
			else
				return direction == OrderDirections.Buy ? security.MarginBuy : security.MarginSell;
		}

		/// <summary>
		/// Получить размер свободных денежных средств в портфеле.
		/// </summary>
		/// <param name="portfolio">Портфель</param>
		/// <param name="useLeverage">Использовать ли для рассчета размер плеча.</param>
		/// <returns>Размер свободных денежных средств.</returns>
		public static decimal GetFreeMoney(this Portfolio portfolio, bool useLeverage = false)
		{
			if (portfolio == null)
				throw new ArgumentNullException("portfolio");

			var freeMoney = portfolio.ExchangeBoard == ExchangeBoard.Forts
				? portfolio.BeginValue - portfolio.CurrentValue + portfolio.VariationMargin
				: portfolio.CurrentValue;

			return useLeverage ? freeMoney * portfolio.Leverage : freeMoney;
		}

		/// <summary>
		/// Получить для непрерывного инструмента реальные экспирирующиеся инструменты.
		/// </summary>
		/// <param name="continuousSecurity">Непрерывный инструмент.</param>
		/// <param name="baseCode">Базовая часть кода инструмента.</param>
		/// <param name="from">Начало диапазона экспираций.</param>
		/// <param name="to">Окончание диапазона экспираций.</param>
		/// <param name="throwIfNotExists">Сгенерировать исключение, если какой-либо из инструментов для переданного <paramref name="continuousSecurity"/> отсутствует.</param>
		/// <returns>Экспирирующиеся инструменты.</returns>
		public static IEnumerable<Security> GetFortsJumps(this ContinuousSecurity continuousSecurity, string baseCode, DateTime from, DateTime to, bool throwIfNotExists = true)
		{
			if (continuousSecurity == null)
				throw new ArgumentNullException("continuousSecurity");

			if (baseCode.IsEmpty())
				throw new ArgumentNullException("baseCode");

			if (from > to)
				throw new ArgumentOutOfRangeException("from");

			var trader = continuousSecurity.CheckTrader();

			for (var year = from.Year; year <= to.Year; year++)
			{
				var monthFrom = year == from.Year ? from.Month : 1;
				var monthTo = year == to.Year ? to.Month : 12;

				for (var month = monthFrom; month <= monthTo; month++)
				{
					char monthCode;

					switch (month)
					{
						case 3:
							monthCode = 'H';
							break;
						case 6:
							monthCode = 'M';
							break;
						case 9:
							monthCode = 'U';
							break;
						case 12:
							monthCode = 'Z';
							break;
						default:
							continue;
					}

					var yearStr = year.To<string>();
					var code = baseCode + monthCode + yearStr.Substring(yearStr.Length - 1, 1);

					foreach (var security in trader.Securities)
					{
						if (security.Code.CompareIgnoreCase(code))
							yield return security;
					}

					if (throwIfNotExists)
						throw new InvalidOperationException("Инструмент с кодом {0} не найден.".Put(code));
				}
			}
		}

		/// <summary>
		/// Заполнить переходы <see cref="ContinuousSecurity.ExpirationJumps"/>.
		/// </summary>
		/// <param name="continuousSecurity">Непрерывный инструмент.</param>
		/// <param name="baseCode">Базовая часть кода инструмента.</param>
		/// <param name="from">Начало диапазона экспираций.</param>
		/// <param name="to">Окончание диапазона экспираций.</param>
		public static void FillFortsJumps(this ContinuousSecurity continuousSecurity, string baseCode, DateTime from, DateTime to)
		{
			var securities = continuousSecurity.GetFortsJumps(baseCode, from, to);

			foreach (var security in securities)
			{
				if (security.ExpiryDate == null)
					throw new InvalidOperationException("Инструмент {0} не имеет информацию о времени экспирации.".Put(security.Id));

				continuousSecurity.ExpirationJumps.Add(security, (DateTime)security.ExpiryDate);
			}
		}

		private sealed class CashPosition : Position, IDisposable
		{
			private readonly Portfolio _portfolio;

			public CashPosition(Portfolio portfolio)
			{
				if (portfolio == null)
					throw new ArgumentNullException("portfolio");

				_portfolio = portfolio;

				Portfolio = _portfolio;
				Security = new Security
				{
					Id = _portfolio.Name,
					Name = _portfolio.Name,
				};

				UpdatePosition();

				_portfolio.Trader.PortfoliosChanged += TraderOnPortfoliosChanged;
			}

			private void UpdatePosition()
			{
				BeginValue = _portfolio.BeginValue;
				CurrentValue = _portfolio.CurrentValue;
				BlockedValue = _portfolio.Commission;
			}

			private void TraderOnPortfoliosChanged(IEnumerable<Portfolio> portfolios)
			{
				if (portfolios.Contains(_portfolio))
					UpdatePosition();
			}

			void IDisposable.Dispose()
			{
				_portfolio.Trader.PortfoliosChanged -= TraderOnPortfoliosChanged;
			}
		}

		/// <summary>
		/// Сконвертировать портфель в денежную позицию.
		/// </summary>
		/// <param name="portfolio">Портфель с торговым счетом.</param>
		/// <returns>Денежная позиция.</returns>
		public static Position ToCashPosition(this Portfolio portfolio)
		{
			return new CashPosition(portfolio);
		}

		private sealed class NativePositionManager : IPositionManager
		{
			private readonly Position _position;

			public NativePositionManager(Position position)
			{
				if (position == null)
					throw new ArgumentNullException("position");

				_position = position;
			}

			/// <summary>
			/// Суммарное значение позиции.
			/// </summary>
			decimal IPositionManager.Position
			{
				get { return _position.CurrentValue; }
				set { throw new NotSupportedException(); }
			}

			event Action<Position> IPositionManager.NewPosition
			{
				add { }
				remove { }
			}

			event Action<Position> IPositionManager.PositionChanged
			{
				add { }
				remove { }
			}

			/// <summary>
			/// Рассчитать позицию по заявке.
			/// </summary>
			/// <param name="order">Заявка.</param>
			/// <returns>Позиция по заявке.</returns>
			decimal IPositionManager.ProcessOrder(Order order)
			{
				throw new NotSupportedException();
			}

			/// <summary>
			/// Рассчитать позицию по сделке.
			/// </summary>
			/// <param name="trade">Сделка.</param>
			/// <returns>Позиция по сделке.</returns>
			decimal IPositionManager.ProcessMyTrade(MyTrade trade)
			{
				throw new NotSupportedException();
			}


			IEnumerable<Position> IPositionManager.Positions
			{
				get
				{
					throw new NotSupportedException();
				}
				set
				{
					throw new NotSupportedException();
				}
			}

			void IPositionManager.Reset()
			{
				throw new NotSupportedException();
			}
		}

		/// <summary>
		/// Сконвертировать позицию в объект типа <see cref="IPositionManager"/>.
		/// </summary>
		/// <param name="position">Позиция.</param>
		/// <returns>Менеджера расчета позиции.</returns>
		public static IPositionManager ToPositionManager(this Position position)
		{
			if (position == null)
				throw new ArgumentNullException("position");

			return new NativePositionManager(position);
		}

		private sealed class EquityStrategy : Strategy
		{
			private readonly Dictionary<DateTime, Order[]> _orders;
			private readonly Dictionary<Tuple<Security, Portfolio>, Strategy> _childStrategies = new Dictionary<Tuple<Security, Portfolio>, Strategy>(); 

			public EquityStrategy(IEnumerable<Order> orders, IDictionary<Security, decimal> openedPositions)
			{
				_orders = orders.GroupBy(o => o.Time).ToDictionary(g => g.Key, g => g.ToArray());

				_childStrategies = orders.ToDictionary(GetKey, o => new Strategy
				{
					Portfolio = o.Portfolio,
					Security = o.Security,
					Position = openedPositions.TryGetValue2(o.Security) ?? 0,
				});

				ChildStrategies.AddRange(_childStrategies.Values);
			}

			protected override void OnStarted()
			{
				base.OnStarted();

				Security
					.WhenTimeCome(_orders.Keys)
					.Do(time => _orders[time].ForEach(o => _childStrategies[GetKey(o)].RegisterOrder(o)))
					.Apply(this);
			}

			private static Tuple<Security, Portfolio> GetKey(Order order)
			{
				return new Tuple<Security, Portfolio>(order.Security, order.Portfolio);
			}
		}

		/// <summary>
		/// Сэмулировать заявки на истории.
		/// </summary>
		/// <param name="orders">Заявки, которые необходимо сэмулировать на истории.</param>
		/// <param name="storageRegistry">Внешнеее хранилище для доступа к исторических данным.</param>
		/// <param name="openedPositions">Сделки, описывающие начальные открытые позиции.</param>
		/// <returns>Виртуальная стратегии, содержащая в себе ход эмуляционных торгов.</returns>
		public static Strategy EmulateOrders(this IEnumerable<Order> orders, IStorageRegistry storageRegistry, IDictionary<Security, decimal> openedPositions)
		{
			if (openedPositions == null)
				throw new ArgumentNullException("openedPositions");

			if (storageRegistry == null)
				throw new ArgumentNullException("storageRegistry");

			if (orders == null)
				throw new ArgumentNullException("orders");

			if (orders.IsEmpty())
				throw new ArgumentOutOfRangeException("orders");

			using (var trader = new RealTimeEmulationTrader<DataFeedTrader>(new DataFeedTrader(orders.Select(o => o.Security).Distinct(), orders.Select(o => o.Portfolio).Distinct())
			{
				StorageRegistry = storageRegistry
			}))
			{
				var from = orders.Min(o => o.Time).Date;
				var to = from + TimeSpanHelper.LessOneDay;

				var strategy = new EquityStrategy(orders, openedPositions) { Trader = trader };

				var waitHandle = new object();

				trader.UnderlyingTrader.StateChanged += (oldState, newState) =>
				{
					if (trader.UnderlyingTrader.State == EmulationStates.Started)
						strategy.Start();

					if (trader.UnderlyingTrader.State == EmulationStates.Stopped)
					{
						strategy.Stop();

						lock (waitHandle)
							Monitor.Pulse(waitHandle);
					}
				};

				trader.Connect();
				trader.StartExport();

				trader.UnderlyingTrader.Start(from, to);

				lock (waitHandle)
				{
					if (trader.UnderlyingTrader.State != EmulationStates.Stopped)
						Monitor.Wait(waitHandle);
				}

				return strategy;
			}
		}

		/// <summary>
		/// Записать сообщение о заявке в лог.
		/// </summary>
		/// <param name="receiver">Получатель логов.</param>
		/// <param name="order">Заявка.</param>
		/// <param name="operation">Операция, которая проводится в заявокй.</param>
		/// <param name="getAdditionalInfo">Дополнительная информация о заявке.</param>
		public static void AddOrderInfoLog(this ILogReceiver receiver, Order order, string operation, Func<string> getAdditionalInfo = null)
		{
			receiver.AddOrderLog(LogLevels.Info, order, operation, getAdditionalInfo);
		}

		/// <summary>
		/// Записать ошибку о заявке в лог.
		/// </summary>
		/// <param name="receiver">Получатель логов.</param>
		/// <param name="order">Заявка.</param>
		/// <param name="operation">Операция, которая проводится в заявокй.</param>
		/// <param name="getAdditionalInfo">Дополнительная информация о заявке.</param>
		public static void AddOrderErrorLog(this ILogReceiver receiver, Order order, string operation, Func<string> getAdditionalInfo = null)
		{
			receiver.AddOrderLog(LogLevels.Error, order, operation, getAdditionalInfo);
		}

		private static void AddOrderLog(this ILogReceiver receiver, LogLevels type, Order order, string operation, Func<string> getAdditionalInfo)
		{
			if (receiver == null)
				throw new ArgumentNullException("receiver");

			if (order == null)
				throw new ArgumentNullException("order");

			var orderDescription = order.ToString();
			var additionalInfo = getAdditionalInfo == null ? string.Empty : getAdditionalInfo();

			receiver.AddLog(new LogMessage(receiver, receiver.CurrentTime, type, () => "{0}: {1} {2}".Put(operation, orderDescription, additionalInfo)));
		}
	}
}